/****************************************************************************
 *
 * Copyright (C) 2003-2010 Los Alamos National Security, LLC
 *                         Packet Analytics Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 2 as
 * published by the Free Software Foundation.  You may not use, modify or
 * distribute this program under any other version of the GNU General
 * Public License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 ****************************************************************************/
package nfse.gui.server;

import java.io.File;
import java.io.FileInputStream;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.util.zip.GZIPInputStream;
import nfse.NetFSE;
import nfse.NetFSERecord;
import nfse.NetFSERecordFactory;
import nfse.NetFSEUtil;

import nfse.gui.record.FlexRecord;
import nfse.gui.record.FlexRecordField;
import nfse.stats.StatsEntry;
import nfse.stats.StatsServer;

public abstract class GUIRecordFactory<E extends NetFSERecord> extends NetFSERecordFactory {

    protected RecordBuilderUtil builder = new RecordBuilderUtil();

    /***************************************************************************
     * This method transforms the parsed event into a FlexRecord for use in
     * displaying the record in the web interface. This method is abstract and
     * therefore must be implemented by each individual event type.
     * 
     * @return The event in the form of a nfse.gui.record.FlexRecord that can be
     *         passed to the web interface.
     */
    public FlexRecord getFlexRecord(E record) {
        setValues(record, -1);
        return builder.getFlexRecord(record.getSensorID());
    }

    protected abstract void setValues(E record, int id);

    public String getInsertFieldTableCopySQL(E record, int id) {
        setValues(record, id);
        return builder.getCopySQL();
    }

    /***************************************************************************
     * This method transforms the parsed event into an array of FlexRecordFields
     * that contains information on column names, types and display criteria.
     * This method is abstract and therefore must be implemented by each
     * individual event type extending NetFSERecord.
     * 
     * @return The event type information in the form of a
     *         nfse.gui.record.FlexRecordField array that can be passed to the
     *         web interface.
     */
    public FlexRecordField[] getFlexRecordFields() {
        return builder.getFlexRecordFields();
    }

    /***************************************************************************
     * This method is called when inserting records into the results database
     * following a search.
     * 
     * @param id
     *            The record ID to use for this entry. This value may become
     *            unnecessary in future versions.
     * @param table
     *            The table in the results database that this record will be
     *            inserted into.
     * @return An SQL INSERT statement for this event instance.
     */
    public String getInsertFieldTableSQL(E record, int id, String table) {
        setValues(record, id);
        return builder.getInsertSQL(table);
    }

    /***************************************************************************
     * Search results are stored in relational database tables, one for each
     * event type specified in a search. Each table contains columns for all
     * fields that are to be displayed in the web interface.
     * 
     * @param table
     *            The result table name into which records will be inserted
     *            following a search.
     * @return A CREATE TABLE SQL statement for the result table used to store
     *         search results for presentation in the web interface.
     */
    public String getCreateFieldTableSQL(String table) {
        return builder.getCreateSQL(table);
    }

    /***************************************************************************
     * This method returns the fields in the event type as a comma separated
     * value String. This method can be overridden but the base implementation
     * calls getFlexRecordFields(), which is suitable in most cases.
     * 
     * @return The event type as a CSV String.
     */
    public String toCSV(E record) {
        String temp = "";
        FlexRecord flex = getFlexRecord(record);
        for (int i = 0; i < flex.values.length; i++) {
            if (i > 0)
                temp += ",";
            temp += flex.values[i];
        }
        return temp;
    }

    /***************************************************************************
     * Displays the event as a CSV by calling toCSV(). Can be overridden in
     * cases where a CSV representation is not desirable.
     * 
     * @return The event type as a CSV String.
     */
    public String toString(E record) {
        return toCSV(record);
    }

    public static void setSpecialValues(FlexRecord flexRecord, FlexRecordField[] fields) {
        flexRecord.specialValues = new String[flexRecord.values.length];
        for (int i = 0; i < flexRecord.specialValues.length; i++) {
            if (fields[i].type == FlexRecordField.TYPE_STRING) {
                if (fields[i].subType == FlexRecordField.SUB_TYPE_IP_ADDR) {
                    flexRecord.specialValues[i] = NetFSE.IP_LINK_FACTORY.generateLink(flexRecord.values[i]);
                }
            } else if (fields[i].type == FlexRecordField.TYPE_INT) {

            }
        }
    }

    /***************************************************************************
     * This method returns the field names in the event type as a comma
     * separated value String. Used in displaying the column names at the
     * beginning of a CSV file. This method can be overridden but the base
     * implementation calls getFlexRecordFields(), which is suitable in most
     * cases.
     * 
     * @return The event type field names as a CSV String.
     */
    public String getCSVColNames() {
        FlexRecordField[] fields = this.getFlexRecordFields();
        String colNames = "";
        for (int i = 0; i < fields.length; i++) {
            if (i > 0)
                colNames += ",";
            colNames += fields[i].columnName;
            switch (fields[i].type) {
            case FlexRecordField.TYPE_BOOLEAN:
                colNames += "_b";
                break;
            case FlexRecordField.TYPE_INT:
                colNames += "_i";
                break;
            case FlexRecordField.TYPE_LONG:
                colNames += "_l";
                break;
            case FlexRecordField.TYPE_FLOAT:
                colNames += "_f";
                break;
            default:
                colNames += "_s";
                break;
            }
        }
        return colNames;
    }

    /***************************************************************************
     * This method is called to index search result tables in the results
     * database. The fields indexed are taken from getFlexRecordFields(). This
     * method is not typically overridden.
     * 
     * @param table
     *            The table name of the result table that is to be indexed.
     * @return A String array of SQL CREATE INDEX statements.
     */
    public String[] getIndexFieldTableSQL(String table) {
        FlexRecordField[] fields = this.getFlexRecordFields();
        String[] temp = new String[fields.length];
        for (int i = 0; i < fields.length; i++)
            temp[i] = "create index " + table + "_" + fields[i].columnName + "_idx on " + table + " ("
                    + fields[i].columnName + ")";
        return temp;
    }

    public static void main(String[] args) throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        if (args.length == 2) {
            NetFSERecordFactory rec = (NetFSERecordFactory) Class.forName(args[0]).newInstance();
            rec.parse(args);
        } else if (args.length >= 2) {
            if (args[0].equals("--html")) {
                GUIRecordFactory rec = (GUIRecordFactory) Class.forName(args[1]).newInstance();
                rec.outputHTML(args);
            } else if (args[0].equals("--parse")) {
                NetFSERecordFactory rec = (NetFSERecordFactory) Class.forName(args[1]).newInstance();
                String[] tempArgs = new String[args.length - 2];
                for (int i = 2; i < args.length; i++) {
                    tempArgs[i - 2] = args[i];
                }
                rec.parse(tempArgs);
            }
        }
    }

    public void outputHTML(String[] args) {
        GUIRecordFactory rec = this;
        try {

            File f = new File(args[2]);
            if (!f.exists()) {
                System.err.println("The file specified does not exist: '" + args[2] + "'.");
                throw (new Exception());
            }

            int fileCount = 0;
            File outFile = new File(args[3]);
            File outFile2 = null;
            PrintWriter indexOut = new PrintWriter(outFile);
            PrintWriter out2 = null;

            int recPerPage = 200;
            int maxRec = -1;

            recPerPage = Integer.parseInt(args[4]);
            maxRec = Integer.parseInt(args[5]);

            String keyword = null;
            boolean isSyslog = rec.isSyslog();
            if (isSyslog) {
                keyword = args[6];
            }

            indexOut.println("<html><body>");

            long count = 0;
            long bad = 0;
            long good = 0;
            long start = System.currentTimeMillis();
            if (f.getName().endsWith(".gz")) {
                GZIPInputStream in = new GZIPInputStream(new FileInputStream(f));
                String recordLine = rec.getRecordLine(in);
                while (recordLine != null) {
                    if (out2 == null || (count > 0 && count % recPerPage == 0)) {
                        if (out2 != null) {
                            out2.println("</table>");
                            if (fileCount > 1)
                                out2.println("<a href=\"" + outFile.getAbsolutePath() + "." + (fileCount - 1)
                                        + ".html\">Prev</a> - ");
                            out2.println("<a href=\"" + outFile.getAbsolutePath() + "." + (fileCount + 1)
                                    + ".html\">Next</a>");
                            out2.println("</table></body></html>");
                            out2.flush();
                            out2.close();

                        }
                        outFile2 = new File(outFile.getAbsolutePath() + "." + (++fileCount) + ".html");
                        out2 = new PrintWriter(outFile2);
                        // Print the column headers
                        out2.println("<html><body><table>");
                        out2.println("<tr>");
                        FlexRecordField[] fields = ((GUIRecordFactory) rec).getFlexRecordFields();
                        for (FlexRecordField field : fields) {
                            out2.println("<td><a href=\"#" + field.columnName + "\">" + field.name + "</a></td>");
                        }
                        out2.println("</tr>");

                        indexOut.println("<a href=\"" + outFile2.getAbsolutePath() + "\">File #" + fileCount
                                + "</a><br>");
                    }
                    try {
                        boolean matches = false;
                        if (isSyslog) {
                            int tagIdx = recordLine.indexOf(keyword);
                            try {
                                int tagIdx2 = recordLine.indexOf(" ", tagIdx + keyword.length());
                                String tag = recordLine.substring(tagIdx, tagIdx2);
                                // System.out.println(tag);
                                if (tag.endsWith(":")) {
                                    matches = true;
                                    recordLine = recordLine.substring(tagIdx2).trim();
                                }
                            } catch (Exception e) {
                                // e.printStackTrace();
                            }
                        }

                        count++;
                        NetFSERecord rec2 = (NetFSERecord) rec.generate(recordLine, -1);
                        if (rec2 == null)
                            throw (new Exception("The record could not be parsed"));
                        FlexRecord flexRec = rec.getFlexRecord(rec2);
                        for (String value : flexRec.values) {
                            System.out.println("<td>" + value + "</td>");
                        }
                        good++;
                    } catch (Exception e) {
                        bad++;
                        e.printStackTrace();
                        System.err.println("-- Start bad record --\n" + recordLine + "\n-- End bad record --");
                    }
                    recordLine = rec.getRecordLine(in);
                }
                in.close();
            } else {
                RandomAccessFile raf = new RandomAccessFile(f, "r");
                String recordLine = rec.getRecordLine(raf);
                while (recordLine != null) {
                    if (out2 == null || (count > 0 && count % recPerPage == 0)) {
                        if (out2 != null) {
                            out2.println("</table>");
                            if (fileCount > 1)
                                out2.println("<a href=\"" + outFile.getAbsolutePath() + "." + (fileCount - 1)
                                        + ".html\">Prev</a> - ");
                            out2.println("<a href=\"" + outFile.getAbsolutePath() + "." + (fileCount + 1)
                                    + ".html\">Next</a>");
                            out2.println("</table></body></html>");
                            out2.flush();
                            out2.close();

                        }
                        outFile2 = new File(outFile.getAbsolutePath() + "." + (++fileCount) + ".html");
                        out2 = new PrintWriter(outFile2);
                        out2.println("<html><body><table>");
                        indexOut.println("<a href=\"" + outFile2.getAbsolutePath() + "\">File #" + fileCount
                                + "</a><br>");

                        // Print the column headers
                        out2.println("<tr>");
                        FlexRecordField[] fields = ((GUIRecordFactory) rec).getFlexRecordFields();
                        for (FlexRecordField field : fields) {
                            out2.println("<td><a href=\"#" + field.columnName + "\">" + field.name + "</a></td>");
                        }
                        out2.println("</tr>");

                        indexOut.println("<a href=\"" + outFile2.getAbsolutePath() + "\">File #" + fileCount
                                + "</a><br>");
                    }
                    try {
                        boolean matches = false;
                        if (isSyslog) {
                            int tagIdx = recordLine.indexOf(keyword);
                            try {
                                int tagIdx2 = recordLine.indexOf(" ", tagIdx + keyword.length());
                                String tag = recordLine.substring(tagIdx, tagIdx2);
                                if (tag.endsWith(":")) {
                                    matches = true;
                                    recordLine = recordLine.substring(tagIdx2).trim();
                                }
                            } catch (IndexOutOfBoundsException iob) {
                                // no op
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }

                        if (!isSyslog || (isSyslog && matches)) {

                            count++;
                            NetFSERecord rec2 = (NetFSERecord) rec.generate(recordLine, -1);
                            if (rec2 == null)
                                throw (new Exception("The record could not be parsed"));
                            FlexRecord flexRec = rec.getFlexRecord(rec2);
                            out2.println("<tr>");
                            for (String value : flexRec.values) {
                                out2.println("<td>" + value + "</td>");
                            }
                            out2.println("</tr>");
                            good++;
                        }
                    } catch (Exception e) {
                        bad++;
                        e.printStackTrace();
                        System.err.println("-- Start bad record --\n" + recordLine + "\n-- End bad record --");
                    }
                    recordLine = rec.getRecordLine(raf);
                }
                raf.close();
            }
            long stop = System.currentTimeMillis();

            out2.println("</table>");
            out2.println("</body></html>");
            out2.flush();
            out2.close();

            indexOut.println("</body></html>");
            indexOut.flush();
            indexOut.close();

            float sec = (float) ((stop - start) / 1000);
            if (sec == (float) 0.0)
                sec = (float) 1.0;

            System.err.println("       Records: " + NetFSEUtil.formattedInteger(count));
            System.err.println("  Good records: " + NetFSEUtil.formattedInteger(good));
            System.err.println("   Bad records: " + NetFSEUtil.formattedInteger(bad));
            System.err.println("       Seconds: " + NetFSEUtil.formattedDouble(sec));
            System.err.println("Records/Second: " + NetFSEUtil.formattedDouble(count / sec));

        } catch (Exception e) {
            System.err.println("Usage: java " + rec.getClass().getName() + " /data/file/path");
            System.err.println("\tThe data file can be raw logs or gzipped (ending in .gz)");
            // e.printStackTrace();
        }
    }

    public StatsEntry generateStats(E temp) {

        FlexRecordField[] fields = this.getFlexRecordFields();
        FlexRecord record = getFlexRecord(temp);

        int protocol = -1;
        int dstPort = -1;

        long srcBytes = -1;
        long dstBytes = -1;
        long srcPackets = -1;
        long dstPackets = -1;

        int i = 0;
        boolean foundProto = false;
        boolean foundSrcBytes = false;
        boolean foundDstBytes = false;
        boolean foundSrcPackets = false;
        boolean foundDstPackets = false;
        for (FlexRecordField field : fields) {
            if (!foundProto && field.columnName.equals("protocol")) {
                foundProto = true;
                try {
                    protocol = Integer.parseInt(record.values[i]);
                } catch (NumberFormatException nfe) {
                    protocol = NetFSEUtil.getProtocol(record.values[i]);
                }
            } else if (!foundSrcBytes && field.columnName.equals("srcbytes")) {
                foundSrcBytes = true;
                srcBytes = Long.parseLong(record.values[i]);
            } else if (!foundDstBytes && field.columnName.equals("dstbytes")) {
                foundDstBytes = true;
                dstBytes = Long.parseLong(record.values[i]);
            } else if (!foundSrcPackets && field.columnName.equals("srcpackets")) {
                foundSrcPackets = true;
                srcPackets = Long.parseLong(record.values[i]);
            } else if (!foundDstPackets && field.columnName.equals("dstpackets")) {
                foundDstPackets = true;
                dstPackets = Long.parseLong(record.values[i]);
            }
            i++;
        }

        long srcIP = temp.getSrcIP();
        long dstIP = temp.getDstIP();
        short direction = StatsServer.DIRECTION_OUTGOING;
        boolean srcIsHome = false;
        boolean dstIsHome = false;

        if (srcIP != -1) {
            srcIsHome = NetFSE.isHomeNet(srcIP);
            if (srcIsHome)
                direction = StatsServer.DIRECTION_INCOMING;
            else
                direction = StatsServer.DIRECTION_OUTGOING;
        } else if (dstIP != -1) {
            dstIsHome = NetFSE.isHomeNet(dstIP);
            if (dstIsHome)
                direction = StatsServer.DIRECTION_OUTGOING;
            else
                direction = StatsServer.DIRECTION_INCOMING;
        }

        if (srcIsHome && dstIsHome) {
            direction = StatsServer.DIRECTION_INTERNAL;
        }

        StatsEntry stats = new StatsEntry();
        stats.sensorID = temp.getSensorID();
        stats.startTS = temp.getStartTS();
        stats.endTS = temp.getEndTS();
        stats.direction = direction;
        stats.srcIP = srcIP;
        stats.dstIP = dstIP;
        stats.srcBytes = srcBytes;
        stats.dstBytes = dstBytes;
        stats.srcPackets = srcPackets;
        stats.dstPackets = dstPackets;
        stats.protocol = protocol;
        stats.dstPort = temp.getDstPort();

        return stats;
    }

}
